package com.aster.common.api.wechat.util;

import com.aster.common.api.wechat.domain.notify.NotifyReceive;
import com.aster.common.api.wechat.domain.notify.NotifyReply;
import com.aster.common.api.wechat.domain.req.*;
import com.aster.common.api.wechat.domain.resp.*;
import com.aster.common.api.wechat.util.MD5.MD5Util;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.ConnectException;
import java.net.URL;
import java.security.*;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

/**
 * 微信支付工具类
 *
 * @author wangdr
 * @date 2014-11-18
 */
public class WXPayUtil {

	private static Logger log = LoggerFactory.getLogger(WXPayUtil.class);

	// 微信分配的公众账号 ID
	public final static String appId = "wx58989d28b7ceaf1f";
	//微信分配的appsecret
	public final static String appsecret = "f4cf8c984b05c9cb2bea6bae5d1cb668";
	// 微信支付分配的商户号
	public final static String mchId = "1481304802";
	// 商户支付密钥
	public final static String key = "hainanhuidongbangbangxiong66qiao";
	// 支付结果异步通知地址
	public final static String notifyURL = "http://mall.hdbbx.cn/wxpayIface/notify";
	// 商户证书存放路径
	// public final static String CALIB_PATH = "E:/CALib/WeiXin_cert/@mchId/apiclient_cert.p12";
	public final static String CALIB_PATH = "/data/appServer/DuoMi-WeiZhan/CALib/WeiXin_cert/@mchId/apiclient_cert.p12";

	// 统一支付接口地址
	public final static String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
	// 订单查询接口地址
	public final static String ORDERQUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
	// 关闭订单接口地址
	public final static String CLOSEORDER_URL = "https://api.mch.weixin.qq.com/pay/closeorder";
	// 退款接口地址
	public final static String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
	// 退款查询接口地址
	public final static String REFUNDQUERY_URL = "https://api.mch.weixin.qq.com/pay/refundquery";
	// 对账单接口地址
	public final static String DOWNLOADBILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";

	/**
	 * 发起https请求并获取结果
	 *
	 * @param requestUrl 请求地址
	 * @param requestMethod 请求方式（GET、POST）
	 * @param outputXML 提交的XML格式数据
	 * @return String XML格式的字符串
	 */
	public static String HttpsRequest4XML(String requestUrl, String requestMethod, String outputXML) {
		HttpsURLConnection httpsUrlConn = null;
		InputStream inputStream = null;
		InputStreamReader inputStreamReader = null;
		BufferedReader bufferedReader = null;

		String result = null;
		StringBuffer buffer = new StringBuffer();
		try {
			// 创建SSLContext对象，并使用我们指定的信任管理器初始化
			TrustManager[] tm = { new MyX509TrustManager() };
			SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
			sslContext.init(null, tm, new java.security.SecureRandom());
			// 从上述SSLContext对象中得到SSLSocketFactory对象
			SSLSocketFactory ssf = sslContext.getSocketFactory();

			URL url = new URL(requestUrl);
			httpsUrlConn = (HttpsURLConnection) url.openConnection();
			httpsUrlConn.setSSLSocketFactory(ssf);

			httpsUrlConn.setDoOutput(true);
			httpsUrlConn.setDoInput(true);
			httpsUrlConn.setUseCaches(false);
			// 设置请求方式（GET/POST）
			httpsUrlConn.setRequestMethod(requestMethod);

			if ("GET".equalsIgnoreCase(requestMethod)) {
				httpsUrlConn.connect();
			}

			// 当有数据需要提交时
			if (null != outputXML) {
				OutputStream outputStream = httpsUrlConn.getOutputStream();
				// 注意编码格式，防止中文乱码
				outputStream.write(outputXML.getBytes("UTF-8"));
				outputStream.close();
			}

			// 将返回的输入流转换成字符串
			inputStream = httpsUrlConn.getInputStream();
			inputStreamReader = new InputStreamReader(inputStream, "utf-8");
			bufferedReader = new BufferedReader(inputStreamReader);

			String str = null;
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str).append("\n");
			}

			result = buffer.toString();
		} catch (ConnectException ce) {
			log.info("连接超时：{}" + ce);
		} catch (Exception e) {
			log.info("https请求异常：{}" + e.getMessage());
		} finally {
			// 释放资源
			if (null != bufferedReader) {
				try {
					bufferedReader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (null != inputStreamReader) {
				try {
					inputStreamReader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (null != inputStream) {
				try {
					inputStream.close();
					inputStream = null;
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (null != httpsUrlConn) {
				httpsUrlConn.disconnect();
			}
		}
		return result;
	}

	/**
	 * 发起安全的双向认证https请求并获取结果
	 *
	 * @param requestUrl 请求地址
	 * @param calibPath 商户证书存放路径
	 * @param mchId 商户号
	 * @param outputXML 提交的XML格式数据
	 * @return String XML格式的字符串
	 */
	public static String HttpsSecureRequest4XML(String requestUrl, String calibPath, String mchId, String outputXML) {
		KeyStore keyStore = null;
		FileInputStream instream = null;
		try {
			keyStore = KeyStore.getInstance("PKCS12");
			instream = new FileInputStream(new File(calibPath.replace("@mchId", mchId)));
			if (null != keyStore) {
				keyStore.load(instream, mchId.toCharArray());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (null != instream) {
					instream.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		String responseBody = "";
		CloseableHttpClient httpClient = null;
		try {
			// Trust own CA and all self-signed certs
			SSLContext sslcontext = null;
			try {
				sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
			} catch (KeyManagementException e1) {
				e1.printStackTrace();
			} catch (UnrecoverableKeyException e1) {
				e1.printStackTrace();
			} catch (NoSuchAlgorithmException e1) {
				e1.printStackTrace();
			} catch (KeyStoreException e1) {
				e1.printStackTrace();
			}
			// Allow TLSv1 protocol only
			SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" },
					null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
			httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

			HttpPost httpPost = new HttpPost(requestUrl);
			try {
				StringEntity stringEntity = new StringEntity(outputXML, "utf-8");
				httpPost.setHeader("charset", "utf-8");
				httpPost.setEntity(stringEntity);

				HttpResponse httpResponse = httpClient.execute(httpPost);
				HttpEntity entity = httpResponse.getEntity();
				responseBody = EntityUtils.toString(entity, "UTF-8").trim();// 解决相应报文中文乱码问题

				EntityUtils.consume(entity);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				httpPost.abort();
			}
		} finally {
			if (null != httpClient) {
				try {
					httpClient.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		return responseBody;
	}

	/**
	 * 解析微信发来的请求（XML）
	 *
	 * @param request
	 * @return
	 * @throws Exception
	 */
	public static String parseXml(HttpServletRequest request) {

		InputStream inputStream = null;
		InputStreamReader inputStreamReader = null;
		BufferedReader bufferedReader = null;

		String result = null;
		StringBuffer buffer = new StringBuffer();

		try {
			// 从request中取得输入流
			inputStream = request.getInputStream();
			// 将返回的输入流转换成字符串
			inputStreamReader = new InputStreamReader(inputStream, "utf-8");
			bufferedReader = new BufferedReader(inputStreamReader);

			String str = null;
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str);
			}
			result = buffer.toString();
		} catch (ConnectException ce) {
			log.info("连接超时：{}" + ce);
		} catch (Exception e) {
			log.info("https请求异常：{}" + e.getMessage());
		} finally {
			// 释放资源
			if (null != bufferedReader) {
				try {
					bufferedReader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (null != inputStreamReader) {
				try {
					inputStreamReader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (null != inputStream) {
				try {
					inputStream.close();
					inputStream = null;
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

		}

		return result;
	}

	/**
	 * 统一支付接口请求参数对象转换成xml
	 *
	 * @param unifiedOrderRequest 统一支付接口请求参数对象
	 * @return xml
	 */
	public static String unifiedOrderRequestToXml(UnifiedOrderRequest unifiedOrderRequest) {
		xstream.alias("xml", unifiedOrderRequest.getClass());
		return xstream.toXML(unifiedOrderRequest).replace("__", "_");
	}

	/**
	 * xml转换成统一支付接口相应结果对象
	 *
	 * @param responseXML 统一支付接口相应XML格式字符串数据
	 * @return UnifiedOrderResponse
	 */
	public static UnifiedOrderResponse xmlToUnifiedOrderResponse(String responseXML) {
		XStream xs = getXStreamInstance();
		xs.alias("xml", UnifiedOrderResponse.class);
		return (UnifiedOrderResponse) xs.fromXML(responseXML);
	}

	/**
	 * xml转换成支付结果通知接口 接收请求 报文对象
	 *
	 * @param notifyReceiveXML 支付结果通知接口 接收请求 报文XML格式字符串数据
	 * @return NotifyReceive
	 */
	public static NotifyReceive xmlToNotifyReceive(String notifyReceiveXML) {
		XStream xs = getXStreamInstance();
		xs.alias("xml", NotifyReceive.class);
		return (NotifyReceive) xs.fromXML(notifyReceiveXML);
	}

	/**
	 * 支付结果通知接口 响应 对象 报文转换成xml
	 *
	 * @param notifyReply 支付结果通知接口 响应 对象 报文对象
	 * @return xml
	 */
	public static String notifyReplyToXml(NotifyReply notifyReply) {
		xstream.alias("xml", notifyReply.getClass());
		return xstream.toXML(notifyReply).replace("__", "_");
	}

	/**
	 * 订单查询接口请求参数对象转换成xml
	 *
	 * @param orderQueryRequest 订单查询接口请求参数对象
	 * @return xml
	 */
	public static String orderQueryRequestToXml(OrderQueryRequest orderQueryRequest) {
		xstream.alias("xml", orderQueryRequest.getClass());
		return xstream.toXML(orderQueryRequest).replace("__", "_");
	}

	/**
	 * xml转换成订单查询接口相应结果对象
	 *
	 * @param responseXML 订单查询接口相应XML格式字符串数据
	 * @return OrderQueryResponse
	 */
	public static OrderQueryResponse xmlToOrderQueryResponse(String responseXML) {
		XStream xs = getXStreamInstance();
		xs.alias("xml", OrderQueryResponse.class);
		return (OrderQueryResponse) xs.fromXML(responseXML);
	}

	/**
	 * 关闭订单接口请求参数对象转换成xml
	 *
	 * @param closeOrderRequest 关闭订单接口请求参数对象
	 * @return xml
	 */
	public static String closeOrderRequestToXml(CloseOrderRequest closeOrderRequest) {
		xstream.alias("xml", closeOrderRequest.getClass());
		return xstream.toXML(closeOrderRequest).replace("__", "_");
	}

	/**
	 * xml转换成关闭订单接口相应结果对象
	 *
	 * @param responseXML 关闭订单接口相应XML格式字符串数据
	 * @return CloseOrderResponse
	 */
	public static CloseOrderResponse xmlToCloseOrderResponse(String responseXML) {
		XStream xs = getXStreamInstance();
		xs.alias("xml", CloseOrderResponse.class);
		return (CloseOrderResponse) xs.fromXML(responseXML);
	}

	/**
	 * 退款申请接口请求参数对象转换成xml
	 *
	 * @param refundRequest 退款申请接口请求参数对象
	 * @return xml
	 */
	public static String refundRequestToXml(RefundRequest refundRequest) {
		xstream.alias("xml", refundRequest.getClass());
		return xstream.toXML(refundRequest).replace("__", "_");
	}

	/**
	 * xml转换成退款申请接口相应结果对象
	 *
	 * @param responseXML 退款申请接口相应XML格式字符串数据
	 * @return RefundResponse
	 */
	public static RefundResponse xmlToRefundResponse(String responseXML) {
		XStream xs = getXStreamInstance();
		xs.alias("xml", RefundResponse.class);
		return (RefundResponse) xs.fromXML(responseXML);
	}

	/**
	 * 退款查询接口请求参数对象转换成xml
	 *
	 * @param refundQueryRequest 退款查询接口请求参数对象
	 * @return xml
	 */
	public static String refundQueryRequestToXml(RefundQueryRequest refundQueryRequest) {
		xstream.alias("xml", refundQueryRequest.getClass());
		return xstream.toXML(refundQueryRequest).replace("__", "_");
	}

	/**
	 * xml转换成退款查询接口相应结果对象
	 *
	 * @param responseXML 退款查询接口相应XML格式字符串数据
	 * @return RefundQueryResponse
	 */
	public static RefundQueryResponse xmlToRefundQueryResponse(String responseXML) {
		XStream xs = getXStreamInstance();
		xs.alias("xml", RefundQueryResponse.class);
		return (RefundQueryResponse) xs.fromXML(responseXML);
	}

	/**
	 * 对账单接口请求参数对象转换成xml
	 * @return xml
	 */
	public static String downloadBillRequestToXml(DownloadBillRequest downloadBillRequest) {
		xstream.alias("xml", downloadBillRequest.getClass());
		return xstream.toXML(downloadBillRequest).replace("__", "_");
	}

	/**
	 * xml转换成对账单接口相应结果对象
	 *
	 * @param responseXML 对账单接口相应XML格式字符串数据
	 * @return DownloadBillResponse
	 */
	public static DownloadBillResponse xmlToDownloadBillResponse(String responseXML) {
		XStream xs = getXStreamInstance();
		xs.alias("xml", DownloadBillResponse.class);
		return (DownloadBillResponse) xs.fromXML(responseXML);
	}

	/**
	 * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
	 */
	public static String doSign(Object object, String key) {
		Map<String, Object> parameterMap = BeanUtil.transBean2TreeMap(object);
		StringBuffer sb = new StringBuffer();
		Set es = parameterMap.entrySet();
		Iterator it = es.iterator();
		while (it.hasNext()) {
			Map.Entry entry = (Map.Entry) it.next();
			String k = (String) entry.getKey();
			Object v = entry.getValue();
			if ((null != v) && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
				sb.append(k + "=" + v + "&");
			}
		}
		sb.append("key=" + key);
		System.out.println(">>>To Sign String=" + sb.toString());
		String sign = MD5Util.getMd5(sb.toString()).toUpperCase();
		System.out.println(">>>sign=" + sign);
		return sign;
	}

	/**
	 * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
	 */
	public static String doSign(SortedMap parameterMap, String key) {
		StringBuffer sb = new StringBuffer();
		Set es = parameterMap.entrySet();
		Iterator it = es.iterator();
		while (it.hasNext()) {
			Map.Entry entry = (Map.Entry) it.next();
			String k = (String) entry.getKey();
			Object v = entry.getValue();
			if ((null != v) && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
				sb.append(k + "=" + v + "&");
			}
		}
		sb.append("key=" + key);
		System.out.println(">>>To Sign String=" + sb.toString());
		String sign = MD5Util.getMd5(sb.toString()).toUpperCase();
		System.out.println(">>>sign=" + sign);
		return sign;
	}

	/**
	 * 创建SHA1加密串,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
	 */
	public static String doSign4SHA1(SortedMap parameterMap) {
		StringBuffer sb = new StringBuffer();
		Set es = parameterMap.entrySet();
		Iterator it = es.iterator();
		while (it.hasNext()) {
			Map.Entry entry = (Map.Entry) it.next();
			String k = (String) entry.getKey();
			Object v = entry.getValue();
			if ((null != v) && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
				sb.append(k + "=" + v + "&");
			}
		}
		System.out.println(">>>To Sign String=" + sb.substring(0, (sb.length() - 1)).toString());
		String sign = DecriptUtil.SHA1(sb.substring(0, (sb.length() - 1)).toString());
		System.out.println(">>>sign=" + sign);
		return sign;
	}

	/**
	 * 扩展xstream，使其支持CDATA块
	 * 单例模式
	 * @date 2014-11-18
	 */
	private static XStream xstream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")) {
		@Override
		public HierarchicalStreamWriter createWriter(Writer out) {
			return new PrettyPrintWriter(out) {
				// 对所有xml节点的转换都增加CDATA标记
				boolean cdata = true;

				@Override
				@SuppressWarnings("unchecked")
				public void startNode(String name, Class clazz) {
					super.startNode(name, clazz);
				}

				@Override
				protected void writeText(QuickWriter writer, String text) {
					if (cdata) {
						writer.write("<![CDATA[");
						writer.write(text);
						writer.write("]]>");
					} else {
						writer.write(text);
					}
				}
			};
		}
	});

	/**
	 * 扩展xstream，使其支持CDATA块
	 *
	 * @date 2014-11-18
	 */

	private static XStream getXStreamInstance() {
		XStream xstream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")) {
			@Override
			public HierarchicalStreamWriter createWriter(Writer out) {
				return new PrettyPrintWriter(out) {
					// 对所有xml节点的转换都增加CDATA标记
					boolean cdata = true;

					@Override
					@SuppressWarnings("unchecked")
					public void startNode(String name, Class clazz) {
						super.startNode(name, clazz);
					}

					@Override
					protected void writeText(QuickWriter writer, String text) {
						if (cdata) {
							writer.write("<![CDATA[");
							writer.write(text);
							writer.write("]]>");
						} else {
							writer.write(text);
						}
					}
				};
			}
		});
		return xstream;
	}

}